/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package net.worldscale.xp.wslib.im.jabber.impl;

import java.io.IOException;

import net.worldscale.xp.wslib.im.jabber.JabberClient;
import net.worldscale.xp.wslib.im.jabber.JabberClientException;
import net.worldscale.xp.wslib.im.jabber.JabberException;
import net.worldscale.xp.wslib.im.jabber.JabberID;
import net.worldscale.xp.wslib.wsxml.DomUtil;

import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * 
 * @author Kun
 * 
 *         jabber 收发器
 * 
 */
final class JabberRT implements JabberClient.StreamListener {

	private final WorkSession mWS;
	private final JabberClient.Dispatcher mDispatcher;
	private MyAbstractPhaseListener mPhaseListener;

	public JabberRT(WorkSession ws, JabberClient.Dispatcher dispatcher) {
		this.mWS = ws;
		this.mDispatcher = dispatcher;
		this.setPhaseListener(new MyLoginPhaseListener(this));
	}

	public void onStanza(Element stanza) {
		mPhaseListener.onStanza(stanza);
		mWS.getParentSession().getParentSession().getEventRouter().onStanza(
				stanza);
	}

	public void onStreamClose() {
		mPhaseListener.onStreamClose();
		mWS.getParentSession().getParentSession().getEventRouter()
				.onStreamClose();
	}

	public void onStreamOpen() {
		mPhaseListener.onStreamOpen();
		mWS.getParentSession().getParentSession().getEventRouter()
				.onStreamOpen();
	}

	private void setPhaseListener(MyAbstractPhaseListener pl) {
		if (pl != null) {
			this.mPhaseListener = pl;
			this.mWS.setPhase(pl.getPhase());
		}
	}

	private abstract static class MyAbstractPhaseListener implements
			JabberClient.StreamListener {

		protected final JabberRT mJRT;

		public MyAbstractPhaseListener(JabberRT jrt) {
			mJRT = jrt;
		}

		public abstract int getPhase();
	}

	private static class MyLoginPhaseListener extends MyAbstractPhaseListener {

		private MyMechanismEngine mMechanismEngine;

		public MyLoginPhaseListener(JabberRT jrt) {
			super(jrt);
			System.out.println("<!-- XMPP phase : login -->");
		}

		@Override
		public int getPhase() {
			return JabberClient.PHASE_LOGIN;
		}

		public void onStanza(Element stanza) {
			try {
				final String tag = stanza.getTagName();
				if (tag.equals("stream:features")) {
					this.processStanza_stream_features(stanza);
				} else if (tag.equals("proceed")) {
					this.processStanza_proceed(stanza);
				} else if (tag.equals("challenge")) {
					this.processStanza_challenge(stanza);
				} else if (tag.equals("success")) {
					this.processStanza_success(stanza);
				} else if (tag.equals("iq")) {
					this.processStanza_iq(stanza);
				} else if (tag.equals("message")) {
					this.processStanza_message(stanza);
				} else if (tag.equals("presence")) {
					this.processStanza_presence(stanza);
				} else if (tag.equals("failure")) {
					this.processStanza_failure(stanza);
				} else {
					System.out.println("<!-- XMPP unsupported stanza : " + tag
							+ " -->");
					this.mJRT.mDispatcher.send("</stream:stream>");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		public void onStreamOpen() {
			// throw new RuntimeException("Unsupported op.");
		}

		public void onStreamClose() {
			this.mJRT.setPhaseListener(new MyOfflinePhaseListener(this.mJRT));
		}

		private void processStanza_iq(Element stanza) throws IOException {
			final String iq_type = stanza.getAttribute("type");
			final String iq_id = stanza.getAttribute("id");
			if (containSubElement(stanza, "bind",
					"urn:ietf:params:xml:ns:xmpp-bind")) {
				if (iq_type.equals("result")) {
					// 绑定成功
					final String jid_string;
					jid_string = DomUtil.getTextContent(stanza
							.getElementsByTagName("jid").item(0));
					final JabberID jid = new JabberID(jid_string);
					this.mJRT.mWS.setFullJabberID(jid);
					System.out.println("<!-- bind to full jid : " + jid
							+ " -->");
					// 加载名册
					final Element roster = this.mJRT
							.tryToLoadOrDownloadRoster();
					if (roster != null) {
						// 激活会话
						this.mJRT.sendSession();
					}
				} else if (iq_type.equals("error")) {
					// 绑定失败
				} else {
					System.out.println("<!-- unsupported id type:" + iq_type
							+ " -->");
				}
			} else if (containSubElement(stanza, "query", "jabber:iq:roster")) {
				if (iq_type.equals("result")) {
					// 下载名册成功
					final Element query = (Element) stanza
							.getElementsByTagName("query").item(0);
					this.mJRT.mWS.getParentSession().getRosterModel()
							.setRoster(query);
					// 激活会话
					this.mJRT.sendSession();
				} else if (iq_type.equals("error")) {
					// 下载名册失败
				} else {
					System.out.println("<!-- unsupported id type:" + iq_type
							+ " -->");
				}
			} else {
				if (iq_id.startsWith("session_")) {
					// 激活会话
					if (iq_type.equals("result")) {
						// 成功
						this.mJRT.setPhaseListener(new MyOnlinePhaseListener(
								this.mJRT));
						this.mJRT.sendMyPresence();
						return;
					} else if (iq_type.equals("error")) {
						// 失败
						return;
					} else {
						// unsupported
					}
				}
				final String msg = ("<!-- unsupported iq stanza -->");
				System.out.println(msg);
				throw new RuntimeException(msg);
			}
		}

		private void processStanza_message(Element stanza) throws IOException {
			final String msg = ("<!-- unsupported message stanza -->");
			System.out.println(msg);
			throw new RuntimeException(msg);
		}

		private void processStanza_presence(Element stanza) throws IOException {
			final String msg = ("<!-- unsupported presence stanza -->");
			System.out.println(msg);
			throw new RuntimeException(msg);
		}

		private void processStanza_challenge(Element stanza) throws IOException {
			final String request, response;
			request = DomUtil.getTextContent(stanza);
			response = this.mMechanismEngine.challengeString(this.mJRT.mWS
					.getParentSession().getLoginContext(), request);
			final StringBuffer sbuf = new StringBuffer();
			sbuf.append("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>");
			sbuf.append(response);
			sbuf.append("</response>");
			this.mJRT.mDispatcher.send(sbuf.toString());
		}

		private void processStanza_success(Element stanza) throws IOException {
			System.out.println("<!-- SASL success -->");
			// SASL ok

			// new stream
			String host = this.mJRT.mWS.getParentSession().getLoginContext()
					.getJID().getHost();
			this.mJRT.mDispatcher.openStream(host);
		}

		private void processStanza_failure(Element stanza) throws IOException {
			// 处理失败
			// this.mClient.mCore.send("</stream:stream>");
		}

		private void processStanza_proceed(Element stanza) throws IOException {
			// 客户端和服务器尝试通过已有的TCP连接完成 TLS 握手.
			this.mJRT.mWS.startTLS();
		}

		private void processStanza_stream_features(Element stanza)
				throws IOException, JabberException {
			if (containSubElement(stanza, "starttls",
					"urn:ietf:params:xml:ns:xmpp-tls")) {
				// start TLS
				System.out.println("<!-- start TLS -->");
				this.mJRT.mDispatcher
						.send("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
			} else if (containSubElement(stanza, "mechanisms",
					"urn:ietf:params:xml:ns:xmpp-sasl")) {
				// start SASL
				System.out.println("<!-- start SASL -->");
				// 选择一个可以使用的机制,开始接受挑战
				final Element mechanisms = (Element) stanza
						.getElementsByTagName("mechanisms").item(0);
				final MyMechanism mechanism = MyMechanismManager.getInstance()
						.find(mechanisms);
				if (mechanism == null) {
					throw new JabberClientException(
							"No supported mechanism for auth.");
				}
				System.out.println("<!-- select the mechanism : "
						+ mechanism.getName() + " -->");
				mMechanismEngine = mechanism.newEngine();
				// 向服务器发送选定的机制
				final JabberClient.LoginContext lc = this.mJRT.mWS
						.getParentSession().getLoginContext();
				final StringBuffer sbuf = new StringBuffer();
				sbuf
						.append("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='");
				sbuf.append(mechanism.getName());
				sbuf.append("'>");
				sbuf.append(mMechanismEngine.authString(lc));
				sbuf.append("</auth>");
				this.mJRT.mDispatcher.send(sbuf.toString());
			} else if (containSubElement(stanza, "bind",
					"urn:ietf:params:xml:ns:xmpp-bind")) {
				// bind resource
				final String res = this.mJRT.mWS.getParentSession()
						.getLoginContext().getJID().getResource();
				System.out.println("<!-- bind resource '" + res + "' -->");
				final StringBuffer sbuf = new StringBuffer();
				sbuf.append("<iq type='set' id='");
				sbuf.append(this.mJRT.genStanzaId("bind_"));
				sbuf
						.append("'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>");
				sbuf.append(res);
				sbuf.append("</resource></bind></iq>");
				this.mJRT.mDispatcher.send(sbuf.toString());
			} else {
				System.out
						.println("<!-- cannot process the stream:features stanza -->");
			}
		}
	}

	private int mStanzaIdGenCounter = 0;

	private String genStanzaId(String prefix) {
		return (prefix + (mStanzaIdGenCounter++));
	}

	private static boolean containSubElement(final Element superElement,
			final String tagName, final String xmlns) {
		final NodeList nl = superElement.getElementsByTagName(tagName);
		if (nl.getLength() <= 0) {
			return false;
		}
		final Element subElement = (Element) nl.item(0);
		if (!tagName.equals(subElement.getTagName())) {
			return false;
		}
		if (xmlns != null) {
			if (!xmlns.equals(subElement.getAttribute("xmlns"))) {
				return false;
			}
		}
		return true;
	}

	private static class MyOfflinePhaseListener extends MyAbstractPhaseListener {

		public MyOfflinePhaseListener(JabberRT jrt) {
			super(jrt);
			System.out.println("<!-- XMPP phase : offline -->");
		}

		public void onStanza(Element stanza) {
			throw new RuntimeException("Unsupported op.");
		}

		public void onStreamOpen() {
			this.mJRT.setPhaseListener(new MyLoginPhaseListener(this.mJRT));
		}

		@Override
		public int getPhase() {
			return JabberClient.PHASE_OFFLINE;
		}

		public void onStreamClose() {
			throw new RuntimeException("Unsupported op.");
		}
	}

	private static class MyOnlinePhaseListener extends MyAbstractPhaseListener {

		public MyOnlinePhaseListener(JabberRT jrt) {
			super(jrt);
			System.out.println("<!-- XMPP phase : online -->");
		}

		public void onStanza(Element stanza) {
			try {
				final String tag = stanza.getTagName();
				if (tag.equals("iq")) {
					this.processStanza_iq(stanza);
				} else if (tag.equals("message")) {
					this.processStanza_message(stanza);
				} else if (tag.equals("presence")) {
					this.processStanza_presence(stanza);
				} else {
					System.out.println("<!-- XMPP unsupported stanza : " + tag
							+ " -->");
					this.mJRT.mDispatcher.send("</stream:stream>");
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
		}

		@Override
		public int getPhase() {
			return JabberClient.PHASE_ONLINE;
		}

		private void processStanza_iq(Element stanza) {
			// throw new RuntimeException("Unsupported op.");
		}

		private void processStanza_message(Element stanza) {
			this.mJRT.mWS.getParentSession().getMessageList().add(stanza);
		}

		private void processStanza_presence(Element stanza) {
			// throw new RuntimeException("Unsupported op.");
		}

		public void onStreamOpen() {
			throw new RuntimeException("Unsupported op.");
		}

		public void onStreamClose() {
			this.mJRT.setPhaseListener(new MyOfflinePhaseListener(this.mJRT));
		}
	}

	private Element tryToLoadOrDownloadRoster() throws IOException {
		System.out.println("<!-- try to load or download roster -->");
		final JabberID jid = this.mWS.getFullJabberID();
		final Element roster = this.mWS.getParentSession().getParentSession()
				.getEventRouter().loadRoster(jid);
		if (roster == null) {
			// download roster
			System.out.println("<!-- start download roster -->");
			final StringBuffer sbuf = new StringBuffer();
			sbuf.append("<iq from='");
			sbuf.append(jid.toString());
			sbuf.append("' type='get' id='");
			sbuf.append(this.genStanzaId("roster_"));
			sbuf.append("'><query xmlns='jabber:iq:roster'/></iq>");
			this.mDispatcher.send(sbuf.toString());
		} else {
			System.out.println("<!-- roster is loaded -->");
			this.mWS.getParentSession().getRosterModel().setRoster(roster);
		}
		return roster;
	}

	private void sendSession() throws IOException {
		final StringBuffer sbuf = new StringBuffer();
		sbuf.append("<iq to='");
		sbuf.append(this.mWS.getParentSession().getLoginContext().getJID()
				.getHost());
		sbuf.append("' type='set' id='");
		sbuf.append(this.genStanzaId("session_"));
		sbuf
				.append("'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>");
		this.mDispatcher.send(sbuf.toString());
	}

	private void sendMyPresence() throws IOException {
		final StringBuffer sbuf = new StringBuffer();
		sbuf.append("<presence/>");
		this.mDispatcher.send(sbuf.toString());
	}
}
